package com.github.xzb617.regcenter.eureka;

import com.github.xzb617.except.ServiceException;
import com.github.xzb617.regcenter.RegCenterService;
import com.github.xzb617.regcenter.common.Application;
import com.github.xzb617.regcenter.common.Instance;
import com.github.xzb617.regcenter.common.enums.ServerStatus;
import com.github.xzb617.regcenter.eureka.dto.*;
import com.github.xzb617.utils.QueryParamUtil;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import java.net.ConnectException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class EurekaRegCenterServiceImpl implements RegCenterService {

    private final RestTemplate restTemplate;
    private final static Map<String, Boolean> INSTANCE_STATUS = new ConcurrentHashMap<>();

    public EurekaRegCenterServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public List<Application> getApps(String serverUrl) {
        // 查询 eureka 的 Apps
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Accept", "application/json");
        HttpEntity httpEntity = new HttpEntity(httpHeaders);
        ResponseEntity<EurekaApplicationsWrap> response = null;
        try {
            response = this.restTemplate.exchange(serverUrl+"/apps", HttpMethod.GET, httpEntity, EurekaApplicationsWrap.class);
        } catch (ResourceAccessException rae) {
            throw new ServiceException("无法正常连接到注册中心，请检查注册中心状态是否正常.");
        }
        EurekaApplicationsWrap eurekaApplicationsWrap = response.getBody();
        // 转换数据
        if (eurekaApplicationsWrap == null) {
            return new ArrayList<>(0);
        }
        List<EurekaApplication> applications = eurekaApplicationsWrap.getApplications().getApplication();
        List<Application> applicationList = new ArrayList<>(applications.size());
        for (EurekaApplication application : applications) {
            Application app = new Application();
            app.setId(application.getName());
            List<EurekaInstance> instances = application.getInstance();
            int instanceCount = instances.size();
            app.setInstanceCount(instanceCount);
            int upInstanceCount = this.getUpInstanceCount(instances);
            app.setUpInstanceCount(upInstanceCount);
            app.setDownInstanceCount(instanceCount-upInstanceCount);
            applicationList.add(app);
        }
        return applicationList;
    }

    @Override
    public List<Instance> getInstances(String serverUrl, String appId) {
        // 查询 eureka 的 Instances
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Accept", "application/json");
        HttpEntity httpEntity = new HttpEntity(httpHeaders);
        ResponseEntity<EurekaApplicationWrap> response = this.restTemplate.exchange(serverUrl+"/apps/" + appId, HttpMethod.GET, httpEntity, EurekaApplicationWrap.class);
        EurekaApplicationWrap eurekaApplicationWrap = response.getBody();
        // 数据转换
        if (eurekaApplicationWrap == null) {
            return new ArrayList<>();
        }
        List<EurekaInstance> instances = eurekaApplicationWrap.getApplication().getInstance();
        List<Instance> instanceList = new ArrayList<>(instances.size());
        for (EurekaInstance instance : instances) {
            Instance ins = new Instance();
            ins.setInstanceId(instance.getInstanceId());
            ins.setAppId(instance.getApp());
            ins.setHostName(instance.getHostName());
            ins.setIpAddr(instance.getIpAddr());
            ins.setPort(Integer.parseInt(String.valueOf(instance.getPort().get("$"))));
            Map<String, String> metadata = instance.getMetadata();
            metadata.remove("management.port");
            ins.setMetadata(metadata);
            boolean status = "UP".equalsIgnoreCase(instance.getStatus());
            ins.setStatus(status);
            String statusChangingKey = instance.getApp()+":"+instance.getInstanceId();
            if (INSTANCE_STATUS.containsKey(statusChangingKey)) {
                Boolean lastStatus = INSTANCE_STATUS.get(statusChangingKey);
                if (lastStatus.equals(status)) {
                    ins.setStatusChanging(true);
                } else {
                    ins.setStatusChanging(false);
                    INSTANCE_STATUS.remove(statusChangingKey);
                }
            }
            ins.setLastUpdatedTimestamp(new Date(instance.getLastUpdatedTimestamp()));
            instanceList.add(ins);
        }
        return instanceList;
    }

    @Override
    public boolean modifyMetadata(String serverUrl, String appId, String instanceId, Map<String, String> metadata) {
        String queryString = QueryParamUtil.mapToQueryString(metadata);
        if ("".equals(queryString)) {
            return false;
        }
        String serverUrlForMethod = serverUrl+"/apps/"+appId+"/"+instanceId+"/metadata?"+queryString;
        try {
            ResponseEntity<Object> response = this.restTemplate.exchange(serverUrlForMethod, HttpMethod.PUT, null, Object.class);
            return response.getStatusCode() == HttpStatus.OK;
        } catch (Throwable e) {
            return false;
        }
    }

    @Override
    public boolean modifyStatus(String serverUrl, String appId, String instanceId, ServerStatus serverStatus) {
        String status = serverStatus==ServerStatus.UP?"UP":"OUT_OF_SERVICE";
        String serverUrlForMethod = serverUrl+"/apps/"+appId+"/"+instanceId+"/status?value="+status;
        try {
            ResponseEntity<Object> response = this.restTemplate.exchange(serverUrlForMethod, HttpMethod.PUT, null, Object.class);
            boolean result = response.getStatusCode() == HttpStatus.OK;
            if (result) {
                INSTANCE_STATUS.put(appId+":"+instanceId, serverStatus != ServerStatus.UP);
            }
            return result;
        } catch (Throwable e) {
            return false;
        }
    }

    private int getUpInstanceCount(List<EurekaInstance> instances) {
        return instances.stream()
                .filter(ins -> "UP".equalsIgnoreCase(ins.getStatus()))
                .collect(Collectors.toSet()).size();
    }
}
